/******************************************************************************
 * This file is part of libemb.
 *
 * libemb is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * libemb is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with libemb.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Project: Embedme
 * Author : FergusZeng
 * Email  : cblock@126.com
 * git	  : https://git.oschina.net/cblock/embedme
 * Copyright 2014~2020 @ ShenZhen ,China
*******************************************************************************/
#include "ThreadUtil.h"
#include "Tracer.h"
#include <sys/errno.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/types.h>

namespace libemb{
Mutex::Mutex()
{
	pthread_mutex_init(&m_mutex,NULL);
}

Mutex::~Mutex()
{
	pthread_mutex_destroy(&m_mutex);
}

/**
 *  \brief  互斥锁上锁
 *  \note   none
 */
int Mutex::lock()
{
	return pthread_mutex_lock(&m_mutex);
}
/**
 *  \brief  互斥锁解锁
 *  \note   none
 */
int Mutex::unLock()
{
	return pthread_mutex_unlock(&m_mutex);
}
/**
 *  \brief  互斥锁尝试上锁
 *  \note   none
 */
int Mutex::tryLock()
{
	return pthread_mutex_trylock(&m_mutex);
}

AutoLock::AutoLock(Mutex& mutex):
m_pMutex(&mutex)
{
	m_pMutex->lock();
}

AutoLock::~AutoLock()
{
	if (m_pMutex)
	{
		m_pMutex->unLock();
	}
}

MutexCond::MutexCond()
{
    #if defined OS_CYGWIN || defined OS_UNIX
    pthread_condattr_init(&m_condAttr); 
    pthread_condattr_setclock(&m_condAttr, CLOCK_MONOTONIC);
    #endif
    pthread_cond_init(&m_cond,&m_condAttr);
}

MutexCond::~MutexCond()
{
}
/**
 *  \brief  等待条件变量
 *  \param  int usec,超时时间,<0时阻塞等待,>=0时等待usec
 *  \return 成功等到条件变量返回STATUS_OK,超时返回STATUS_TIMEOUT,失败返回STATUS_ERROR
 *  \note   多个线程不可以同时调用wait()方法,必需使用lock进行互斥,例程:
 *          线程A                              线程B
 *          ...                              ...
 *          cond.lock();               	     cond.lock();      
 *          while(pass==0)                   pass=1;
 *          {                                cond.meet();
 *             cond.wait();                  con.unlock();
 *          }                                ...
 *          cond.unlock();
 *          ...
 */
int MutexCond::wait(int usec)
{
    int rc;
    if (usec>=0)
	{
        struct timespec ts;
        clock_gettime(CLOCK_MONOTONIC, &ts);
		time_t sec = usec/1000000;
		if (sec>0)
        {
        	ts.tv_sec += sec;
		}
		long nsec = (usec%1000000)*1000;
		ts.tv_nsec += nsec;
		if (ts.tv_nsec>=1000000000)
		{
			ts.tv_sec += 1;
			ts.tv_nsec = nsec -1000000000;
		}
    #if defined OS_CYGWIN || defined OS_UNIX
        rc = pthread_cond_timedwait(&m_cond,&m_mutex,&ts);
    #else
        rc = pthread_cond_timedwait_monotonic_np(&m_cond,&m_mutex,&ts);/* for Android bionic pthread */
    #endif
    } 
	else 
	{
        rc = pthread_cond_wait(&m_cond,&m_mutex);
		
    }
    switch (rc) {
    case 0:
        return RC_OK;
    case ETIMEDOUT:
        return RC_TIMEOUT;
    default:
        return RC_ERROR;
    }
}
/**
 *  \brief  满足条件变量,通知等待者
 *  \note   none
 */
int MutexCond::meet()
{
    return (pthread_cond_signal(&m_cond)==0)?RC_OK:RC_ERROR;
	//return (pthread_cond_broadcast(&m_cond)==0)?RC_OK:RC_ERROR;
}


/**
 *  \brief  信号量
 *  \param  name 名称中不能包含'/'
 *  \note   分为无名信号量和有名信号量,无名信号量用于线程间通信,有名信号量用于进程间通信      
 */
Semaphore::Semaphore(const char* name)
{
	if (name!=NULL)
	{
		m_name = std::string(name);
	}
	else
	{
		m_name = "";
	}
}

Semaphore::~Semaphore()
{
}

/**
 *  \brief  打开信号量
 *  \param  value 只有有名信号量存在时,可以不指定该值
 *  \return 成功返回true,失败返回false
 *  \note   信号量存在,则返回,不存在则创建
 */
bool Semaphore::open(int value)
{
	if (m_name.empty())
	{
		if (value<0)
		{
			return false;
		}
		/* 初始化无名信号量 */
		if (-1==sem_init(&m_sem, 0, value))
	    {
	        TRACE_ERR_CLASS("sem init error:%s\n",ERRSTR);
	        return false;
	    }
	}
    else
    {
    	/* 有名信号量,名称必须以'/'开头,see"sem_overview" */
    	m_name = std::string("/")+m_name;
	    sem_t* sem = sem_open(CSTR(m_name),O_CREAT|O_EXCL|O_RDWR,0664,value);
	    if (sem==SEM_FAILED)/* 信号量已存在 */
	    {  
	    	if (value<0)
	    	{
				sem = sem_open(CSTR(m_name),0);
				if (sem==SEM_FAILED)
	            {
	            	TRACE_ERR_CLASS("sem create error:%s\n",ERRSTR);
	            	return false;
				}
			}
			else
	    	{
	    		unlink();/* 删除信号量 */
				sem = sem_open(CSTR(m_name),O_CREAT|O_EXCL|O_RDWR,0664,value);
				if (sem==SEM_FAILED)
	            {
	            	TRACE_ERR_CLASS("sem create error:%s\n",ERRSTR);
	            	return false;
				}
			}
	    }
	    m_sem = *sem;
    }
	return true;
}
/**
 *  \brief  关闭信号量
 *  \param  void
 *  \return 成功返回true,失败返回false
 *  \note   对于有名信号量,关闭信号量不会真正删除信号量,还需要调用unlink()
 */
bool Semaphore::close()
{
	if (m_name.empty())
	{
		if (sem_destroy(&m_sem)!=0)
	    {
	        TRACE_ERR_CLASS("sem destroy error:%s\n",ERRSTR);
	        return false;
	    }
	}
	else
    {
	    if (sem_close(&m_sem)!=0)
	    {
	        TRACE_ERR_CLASS("sem close error:%s\n",ERRSTR);
	        return false;
	    }
		 
	}
	return true;
}

/**
 *  \brief  删除信号量
 *  \param  void
 *  \return 成功返回true,失败返回false
 *  \note   该方法仅对有名信号量有效
 */
bool Semaphore::unlink()
{
	if (!m_name.empty())
	{
		if(sem_unlink(CSTR(m_name))!=0)
		{
			TRACE_ERR_CLASS("sem unlink(%s) error:%s\n",CSTR(m_name),ERRSTR);
	        return false;
		}
	}
	return true;
}
/**
 *  \brief  等待信号量
 *  \param  void
 *  \return 成功返回true,失败返回false
 *  \note   使信号量值减1,如果无资源可申请,则阻塞.
 */
bool Semaphore::wait()
{
    if (0==sem_wait(&m_sem))
    {
        return true;
    }
    return false;
}
/**
 *  \brief  尝试等待信号量
 *  \param  void
 *  \return 成功返回true,失败返回false
 *  \note   使信号量值减1,如果无资源可申请,则返回.
 */
bool Semaphore::tryWait()
{
    if (0==sem_trywait(&m_sem))
    {
        return true;
    }
    return false;
}
/**
 *  \brief  释放信号量
 *  \param  void
 *  \return 成功返回true,失败返回false
 *  \note   使信号量值增加1,释放资源
 */
bool Semaphore::post()
{
    if (0==sem_post(&m_sem))
    {
        return true;
    }
    return false;
}
/**
 *  \brief  获取信号量值
 *  \param  value 当前值
 *  \return 成功返回true,失败返回false
 *  \note   当value>0说明有资源,=0无资源,<0表示有|value|个线程在等待资源
 */
bool Semaphore::getValue(int& value)
{
    if (-1==sem_getvalue(&m_sem,&value))
    {
        return false;
    }
    return true;
}

union semun 
{
	int              val;    /* Value for SETVAL */
	struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
	unsigned short  *array;  /* Array for GETALL, SETALL */
	struct seminfo  *__buf;  /* Buffer for IPC_INFO (Linux-specific) */
};
SemaphoreV::SemaphoreV()
{
}
SemaphoreV::~SemaphoreV()
{
}
bool SemaphoreV::open(int key)
{
	m_sem = semget((key_t)key,1,IPC_CREAT|IPC_EXCL);
	if (m_sem<0)
	{
		/* 已经存在,直接打开并设置信号值为1 */
		m_sem = semget((key_t)key,1,IPC_CREAT);
		union semun sem_union;
		sem_union.val = 1;
		if(semctl(m_sem, 0, SETVAL, sem_union)<0)
		{
			TRACE_ERR_CLASS("semctl error: %s\n",ERRSTR);
			this->close();
			return false;
		}
	}
	if (m_sem<0)
	{
		return false;
	}
	return true;
}

void SemaphoreV::close()
{
	union semun sem_union;
	semctl(m_sem,0,IPC_RMID,sem_union);
	m_sem = -1;
}

bool SemaphoreV::wait()
{
	if (m_sem<0)
	{
		return false;
	}
	struct sembuf semb;
	semb.sem_num = 0;
	semb.sem_op = -1;
	semb.sem_flg = SEM_UNDO; 
	semop(m_sem, &semb, 1);
	return true;
}
bool SemaphoreV::post()
{
	if (m_sem<0)
	{
		return false;
	}
	struct sembuf semb;
	semb.sem_num = 0;
	semb.sem_op = 1;
	semb.sem_flg = SEM_UNDO; 
	semop(m_sem, &semb, 1);
	return true;
}

bool SemaphoreV::getValue(int& value)
{
	union semun sem_union;
	if(semctl(m_sem, 0, GETVAL, sem_union)<0)
	{
		return false;
	}
	value = sem_union.val;
	return true;
}

}